// Tencent is pleased to support the open source community by making
// 蓝鲸智云 - 监控平台/日志平台 (BlueKing - Monitor/Log) available.
// Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. All rights reserved.
// Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://opensource.org/licenses/MIT
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
//

package receiver

import (
	"bytes"
	"context"
	"encoding/binary"
	"math"
	"sync"
	"time"

	"github.com/cespare/xxhash"
	bolt "go.etcd.io/bbolt"

	"github.com/TencentBlueKing/bk-collector/confengine"
)

const (
	KeyHashCode string = "hashcode"
	KeyContent  string = "content"
)

func Hash(b []byte) int64 {
	return int64(xxhash.Sum64(b) % math.MaxInt64)
}

func Int64ToBytes(i int64) []byte {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, uint64(i))
	return buf
}

func BytesToInt64(buf []byte) int64 {
	return int64(binary.BigEndian.Uint64(buf))
}

type Identifier struct {
	// net approach
	BizId     string
	AppId     string
	Namespace string
	Uid       string

	// local approach
	Token    string
	Type     string
	TypeId   string
	Service  string
	Instance string
}

func (id Identifier) NetBytes() []byte {
	var buf bytes.Buffer
	buf.WriteString(id.BizId)
	buf.WriteString(id.AppId)
	buf.WriteString(id.Namespace)
	buf.WriteString(id.Uid)
	return buf.Bytes()
}

type CacheHolder interface {
	SetConfig(k Identifier, v []byte) error
	SetGlobal(v []byte) error
	GetHashCode(k Identifier) (int64, bool, error)
	GetContent(k Identifier) ([]byte, bool, error)
	Close() error
}

type visitTime struct {
	LastVisited int64
	LastUpdated int64
}

type diskCacheHolder struct {
	start    int64
	ctx      context.Context
	cancel   context.CancelFunc
	duration int64
	db       *bolt.DB
	lock     sync.Mutex
	hotkeys  map[Identifier]*visitTime
}

func NewDiskCacheHolder(dir string, duration int64) (CacheHolder, error) {
	db, err := bolt.Open(dir, 0o666, nil)
	if err != nil {
		return nil, err
	}

	ctx, cancel := context.WithCancel(context.Background())
	ch := &diskCacheHolder{
		db:       db,
		ctx:      ctx,
		cancel:   cancel,
		duration: duration,
		hotkeys:  map[Identifier]*visitTime{},
		start:    time.Now().Unix(),
	}
	return ch, nil
}

func (ch *diskCacheHolder) Close() error {
	ch.cancel()
	return ch.db.Close()
}

func (ch *diskCacheHolder) SetGlobal(v []byte) error {
	return nil
}

func (ch *diskCacheHolder) loop() {
	ticker := time.NewTicker(time.Duration(ch.duration) * time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-ch.ctx.Done():
			return

		case <-ticker.C:
			now := time.Now().Unix()
			var ids []Identifier
			ch.lock.Lock()
			for k, v := range ch.hotkeys {
				// 如果距离上一次更新周期已经超过了 duration 则需再次拉取更新
				if now-v.LastUpdated >= ch.duration {
					v.LastUpdated = now
					ids = append(ids, k)
				}
			}
			ch.lock.Unlock()
			ch.fetch(ids)
		}
	}
}

func (ch *diskCacheHolder) cleanup() {
	ticker := time.NewTicker(time.Minute * 5)
	defer ticker.Stop()
	const wait = 6 * 3600

	for {
		select {
		case <-ch.ctx.Done():
			return

		case <-ticker.C:
			now := time.Now().Unix()
			// 启动后 6h 才清理
			if now-ch.start <= wait {
				continue
			}

			var ids []Identifier
			ch.lock.Lock()
			for k, v := range ch.hotkeys {
				// 记录保留的 key
				if now-v.LastVisited < wait {
					ids = append(ids, k)
				}
			}
			ch.lock.Unlock()

		}
	}
}

// 拉取 BSCP 逻辑
func (ch *diskCacheHolder) fetch(ids []Identifier) {
	// step:
	// 1) 拉取元数据
	// 2) 拉取数据内容（制品库）
}

// 持久化清除逻辑
func (ch *diskCacheHolder) removeKeys(ids []Identifier) {
	// step:
	// 1) 把 db 里面所有 key 捞出来
	// 2) 删除不在 ids 里面的 key
}

func (ch *diskCacheHolder) SetConfig(k Identifier, v []byte) error {
	return ch.db.Update(func(tx *bolt.Tx) error {
		hashcodeBucket := tx.Bucket([]byte(KeyHashCode))
		if err := hashcodeBucket.Put(k.NetBytes(), Int64ToBytes(Hash(v))); err != nil {
			return err
		}
		contentBucket := tx.Bucket([]byte(KeyContent))
		return contentBucket.Put(k.NetBytes(), v)
	})
}

func (ch *diskCacheHolder) GetHashCode(k Identifier) (int64, bool, error) {
	b, ok, err := ch.get(k, KeyHashCode)
	if err != nil || !ok {
		return 0, ok, err
	}

	ch.lock.Lock()
	defer ch.lock.Unlock()

	v, ok := ch.hotkeys[k]
	if !ok {
		ch.hotkeys[k] = &visitTime{}
	}
	v.LastVisited = time.Now().Unix()
	return BytesToInt64(b), ok, nil
}

func (ch *diskCacheHolder) GetContent(k Identifier) ([]byte, bool, error) {
	return ch.get(k, KeyContent)
}

func (ch *diskCacheHolder) get(k Identifier, bucketName string) ([]byte, bool, error) {
	var dst []byte
	err := ch.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(bucketName))
		dst = b.Get(k.NetBytes())
		return nil
	})

	return dst, dst != nil, err
}

type memCacheHolder struct {
	content  *confengine.TierConfig
	hashcode *confengine.TierConfig
}

func NewMemCacheHolder() (CacheHolder, error) {
	return &memCacheHolder{
		content:  confengine.NewTierConfig(),
		hashcode: confengine.NewTierConfig(),
	}, nil
}

func (ch *memCacheHolder) GetContent(k Identifier) ([]byte, bool, error) {
	val := ch.content.Get(k.Token, k.Service, k.Instance)
	if val == nil {
		return nil, false, nil
	}
	return val.([]byte), true, nil
}

func (ch *memCacheHolder) GetHashCode(k Identifier) (int64, bool, error) {
	val := ch.hashcode.Get(k.Token, k.Service, k.Instance)
	if val == nil {
		return 0, false, nil
	}
	return val.(int64), true, nil
}

func (ch *memCacheHolder) SetConfig(k Identifier, v []byte) error {
	ch.hashcode.Set(k.Token, k.Type, k.TypeId, Hash(v))
	ch.content.Set(k.Token, k.Type, k.TypeId, v)
	return nil
}

func (ch *memCacheHolder) SetGlobal(v []byte) error {
	ch.hashcode.SetGlobal(Hash(v))
	ch.content.SetGlobal(v)
	return nil
}

func (ch *memCacheHolder) Close() error {
	return nil
}
